package jax;
import haxe.Int32;
import haxe.Int64;
import haxe.macro.Context;
import haxe.macro.Expr;
/**
 * ...
 * @author tom
 */

typedef JInterface = String;
class JVM {
	static var stack:Array<Dynamic>;
	public static var classes = new Hash<jax.lang.JClass>();
	public static var externs = new Hash<Class<Dynamic>>();
	static var CLASS_REF_INDEX = Type.enumIndex(JConstant.classref(0));
	// resolves a reference
	public static inline function resolveRef(c:JConstant):Int {
		return switch(c) {
			case classref(i): i;
			case stringref(i): i;
			default: 0;
		};
	}
	// reads class bytecode into a jax.lang.JClass object
	public static function readClass(b:haxe.io.BytesInput):jax.lang.JClass {
		b.bigEndian = true;
		if(b.readByte() != 0xCA) throw "Malformed magic number in class file";
		if(b.readByte() != 0xFE) throw "Malformed magic number in class file";
		if(b.readByte() != 0xBA) throw "Malformed magic number in class file";
		if(b.readByte() != 0xBE) throw "Malformed magic number in class file";
		var c = new jax.lang.JClass();
		var classrefs = new Array<Int>();
		c.minor_version = b.readInt16();
		c.major_version = b.readInt16();
		c.const_pool_count = b.readInt16();
		c.const_pool=  [null];
		#if debug
			trace("Pool Count: "+c.const_pool_count);
		#end
		while(c.const_pool.length <= c.const_pool_count - 1) {
			var bi = b.readByte();
			var constant = switch(bi) {
				case 1: // string
					JConstant.str(b.readString(b.readInt16()));
				case 2: // string
					JConstant.str(b.readString(b.readInt16()));
				case 3: // 32-bit integer
					JConstant.i32(b.readInt32());
				case 4: // 32-bit float
					JConstant.flt(b.readFloat());
				case 5: // 64-bit int
					var high = b.readInt32();
					var low = b.readInt32();
					#if(js||neko)
						untyped JConstant.i64(new haxe.Int64(high, low));
					#else
						JConstant.i64(cast {high:high, low:low});
					#end
				case 6: // 64-bit float
					JConstant.dbl(b.readDouble());
				case 7: // class reference
					classrefs.push(c.const_pool.length);
					JConstant.classref(b.readInt16());
				case 8: // string reference
					JConstant.stringref(b.readInt16());
				case 9: // field reference; class and name&type
					var nt = b.readInt16();
					JConstant.fieldref(nt, b.readInt16());
				case 10: // method reference; class ref and name&type ref
					var nt = b.readInt16();
					JConstant.methodref(nt, b.readInt16());
				case 11: // interface method reference
					var nt = b.readInt16();
					JConstant.imethodref(nt, b.readInt16());
				case 12: // name and type
					var t = b.readInt16();
					JConstant.nameandtype(t, b.readInt16());
				case 15: // method handle
					var kind = b.readByte();
					JConstant.methodhandle(kind, b.readInt16());
				case 16: // method type
					JConstant.methodtype(b.readInt16());
				default:
					trace("Unknown tag byte: "+StringTools.hex(bi));
					JConstant.str("???");
			};
			#if debug
				trace(c.const_pool.length+"th Constant "+bi+" added: "+Std.string(constant));
			#end
			c.const_pool.push(constant);
		}
		c.access_flags = b.readByte();
		c.bitmask = b.readByte();
		c.this_id = b.readInt16();
		c.super_id = b.readInt16();
		c.interf_count = b.readInt16();
		c.interfaces = [];
		while(c.interfaces.length < c.interf_count) {
			c.interfaces.push(b.readInt16());
		}
		var fields_count = b.readInt16();
		c.cfields = [];
		while(c.cfields.length < fields_count) {
			var field = new JClassField();
			field.access_flags = b.readInt16();
			field.name_index = b.readInt16();
			field.descriptor_index = b.readInt16();
			field.attributes_count = b.readInt16();
			field.attributes = [];
			while(field.attributes.length < field.attributes_count) {
				field.attributes.push(readAttribute(c.const_pool, b));
			}
			c.cfields.push(field);
		}
		var methods_count = b.readInt16();
		c.methods = [];
		while(c.methods.length < methods_count) {
			var method = new JMethod();
			method.access_flags = b.readInt16();
			method.name_index = b.readInt16();
			method.descriptor_index = b.readInt16();
			method.attributes_count = b.readInt16();
			method.attributes = [];
			while(method.attributes.length < method.attributes_count) {
				method.attributes.push(readAttribute(c.const_pool, b));
			}
			c.methods.push(method);
		}
		b.close();
		var tname = toNativeValue(toValue(c.const_pool[resolveRef(c.const_pool[c.this_id])]));
		
		c.initc();
		classes.set(tname, c);
		return c;
	}
	static function readAttribute(pool:Array<JConstant>, i:haxe.io.BytesInput):JAttribute {
		var name = pool[i.readInt16()];
		var length = Int32.toInt(i.readInt32());
		var bo = new haxe.io.BytesOutput();
		for(i in 0...length)
			bo.writeByte(0);
		bo.close();
		var bytes = bo.getBytes();
		i.readBytes(bytes, 0, length);
		
		return switch(name) {
			case JConstant.str(s):
				switch(s) {
					case "Code":
						var code = new JCode();
						var bi = new haxe.io.BytesInput(bytes);
						bi.bigEndian = true;
						code.max_stack = bi.readInt16();
						code.max_locals = bi.readInt16();
						var code_length = Int32.toInt(bi.readInt32());
						var codei = new haxe.io.BytesBuffer();
						while(code_length-- > 0)
							codei.addByte(bi.readByte());
						code.code = codei.getBytes();
						var trys_length = bi.readInt16();
						code.trys = [];
						for(i in 0...trys_length) {
							code.trys[i] = new JTryCatch();
							code.trys[i].start_pc = bi.readInt16();
							code.trys[i].end_pc = bi.readInt16();
							code.trys[i].handler_pc = bi.readInt16();
							code.trys[i].catch_type = bi.readInt16();
						}
						var attributes_count = bi.readInt16();
						code.attributes = [];
						for(i in 0...attributes_count) {
							code.attributes[i] = readAttribute(pool, bi);
						}
						JAttribute.Code(code);
					case "Synthetic": JAttribute.Synthetic;
					case "ConstantValue": JAttribute.ConstantValue;
					case "SourceFile": JAttribute.SourceFile(bytes.readString(0, bytes.length));
					default: null;
				}
			default: null;
		};
	}
	public static function toNativeValue(v:JValue):Dynamic {
		var r:Dynamic = null;
		#if debug
			trace("Converting "+v+" to native");
		#end
		switch(v) {
			case _array(v): r=Lambda.array(Lambda.map(v.arr, toNativeValue));
			case _exter(v):
				switch(Type.getClass(v)) {
					case jax.lang.JString: r = cast(v, jax.lang.JString).val;
					default: r = v;
				};
			case _int(v): r=v;
			case _long(v): r=v;
			case _byte(v): r=v;
			case _short(v): r=v;
			case _float(v): r=v;
			case _double(v): r=v;
			case _char(v): r=v;
			case _boolean(v): r=v;
			case _object(v): if (v._type == "String")
					r= cast(v, jax.lang.JString).val;
				else
					r = v;
			case _void: Void;
		};
		#if(debug && !neko)
			trace(r);
		#end
		return r;
	}
	static function runClass(name) {
		var c:jax.lang.JClass = classes.get(name);
		for(m in c.methods) {
			var name = stringConst(c.const_pool[ m.name_index]);
			if(name == "main") {
				for(att in m.attributes) {
					if(att == null)
						continue;
					switch(att) {
						case Code(b): 
							var bi = new haxe.io.BytesInput(b.code);
							var s = "";
							try {
								while(true) {
									var ms= StringTools.hex(bi.readByte());
									if(ms.length < 2)
										ms = "0" + ms;
									s += ms + " ";
								}
							} catch(d:haxe.io.Eof){bi.close();}
							exec(c, b.code, []);
						default: 
					}
				}
			}
		}
	}
	public static inline function gc() {
		#if cpp
			cpp.vm.Gc.run(false);
		#elseif neko
			neko.vm.Gc.run(false);
		#elseif flash
			untyped System.gc();
		#elseif php
			var vs:Int = untyped PHP_MAJOR_VERSION + PHP_VERSION / 10;
			if(vs >= 5.3) {
				untyped gc_enable(); // Enable Garbage Collector
				untyped gc_collect_cycles(); // Run GCs
				untyped gc_disable(); // Disable Garbage Collector
			}
		#elseif debug
			trace("Attempt to garbage collect on unsupported platform.");
		#end
	}
	static function readJarPath(p:String) {
		var bi:haxe.io.BytesInput = null;
		readJar(bi);
	}
	static function readJar(bi:haxe.io.BytesInput) {
		var data = new format.zip.Reader(bi).read();
		var manifest:Hash<String> = null;
		for(d in data) {
			if(StringTools.endsWith(d.fileName, ".class")) {
				readClass(new haxe.io.BytesInput(d.data));
			} else {
				if(d.fileName.toLowerCase() == "meta-inf/manifest.mf") {
					var bi = new haxe.io.BytesInput(d.data);
					manifest = JProperties.read(bi.readString(d.dataSize));
					bi.close();
				}
			}
			JVM.gc();
		}
		if(manifest.exists("Class-Path")) {
			var jars = manifest.get("Class-Path").split(" ");
			for(jar in jars) {
				jar = StringTools.trim(jar);
				readJarPath(jar);
			}
		}
		if(manifest.exists("Main-Class"))
			runClass(StringTools.replace(manifest.get("Main-Class"), ".", "/"));
	}
	static inline function addClass(c:Class<Dynamic>) {
		var name = StringTools.replace(StringTools.replace(Type.getClassName(c), ".", "/"), "jax", "java");
		var names= name.split("/");
		var aname = names[names.length-1];
		if(aname.indexOf("J") == 0)
			aname = aname.substring(1);
		name = name.substr(0, name.lastIndexOf("/")) + "/"+aname;
		JVM.externs.set(name, c);
		#if debug
			trace("External Class added: "+name);
		#end
	}
	static function init() {
		addClass(jax.lang.JAutoClosable);
		addClass(jax.lang.JBoolean);
		addClass(jax.lang.JException);
		addClass(jax.lang.JNullPointerException);
		addClass(jax.lang.JThrowable);
		addClass(jax.lang.JCharSequence);
		addClass(jax.lang.JStringBuilder);
		addClass(jax.lang.JString);
		addClass(jax.lang.JSystem);
		addClass(jax.lang.JObject);
		addClass(jax.lang.JRuntime);
		addClass(jax.io.JPrintStream);
		addClass(jax.io.JCloseable);
		addClass(jax.io.JReader);
		addClass(jax.io.JReadable);
		addClass(jax.io.JConsole);
		addClass(jax.io.JDataInput);
		addClass(jax.io.JFile);
		#if debug
			trace("External Classes: "+Std.string(JVM.externs.keys()));
		#end
	}
	public static function main() {
		init();
		var main = "HelloWorld";
		JVM.readClass(new haxe.io.BytesInput(haxe.Resource.getBytes("class")));
		JVM.runClass(main);
	}
	public static function toValue(c:JConstant, ?pool):JValue {
		return switch(c) {
			case i32(v): JValue._int(v);
			case i64(v): JValue._long(v);
			case flt(v): JValue._float(v);
			case dbl(v): JValue._double(v);
			case str(v): JValue._exter(new jax.lang.JString(v));
			case stringref(v): toValue(pool[v], pool);
			default: throw "Cannot turn "+Std.string(c)+" into value";
		};
	}
	// Executes the bytecode in 'b' with the class 'c' and the args/local vars 'lvars' (optional)
	public static function exec(c:jax.lang.JClass, b:haxe.io.Bytes, ?lvars:Array<Dynamic>=null):JValue {
		#if debug
			var fbi = new haxe.io.BytesInput(b),s = "";
			try {
				while(true) {
					var v = StringTools.hex(fbi.readByte());
					if(v.length == 1)
						v = "0" + v;
					s += v + " ";
				}
			} catch(d:Dynamic) {
				trace("Bytes: "+s);
			}
		#end
		var stack = new Array<JValue>();
		var pool = c.const_pool;
		if(lvars == null)
			lvars = [];
		var bi = new haxe.io.BytesInput(b);
		var bof:Int = -1;
		bi.bigEndian = true;
		try {
			while(true) {
				bof++;
				var byte = bi.readByte();
				#if debug
					trace("Reading byte: "+StringTools.hex(byte));
				#end
				switch(byte) {
					case 0x00: 
						#if debug
							trace("No operation");
						#end
					case 0x01: stack.push(JValue._object(null));
					case 0x02: stack.push(JValue._int(Int32.ofInt(-1)));
					case 0x03: stack.push(JValue._int(Int32.ofInt(0)));
					case 0x04: stack.push(JValue._int(Int32.ofInt(1)));
					case 0x05: stack.push(JValue._int(Int32.ofInt(2)));
					case 0x06: stack.push(JValue._int(Int32.ofInt(3)));
					case 0x07: stack.push(JValue._int(Int32.ofInt(4)));
					case 0x08: stack.push(JValue._int(Int32.ofInt(5)));
					case 0x09: stack.push(JValue._long(Int64.ofInt(0)));
					case 0x0a: stack.push(JValue._long(Int64.ofInt(1)));
					case 0x0b: stack.push(JValue._float(0.0));
					case 0x0c: stack.push(JValue._float(1.0));
					case 0x0d: stack.push(JValue._float(2.0));
					case 0x0e: stack.push(JValue._double(0.0));
					case 0x0f: stack.push(JValue._double(1.0));
					case 0x10: bof++; stack.push(JValue._int(Int32.ofInt(bi.readByte())));
					case 0x11: bof+=2; stack.push(JValue._short(bi.readInt16()));
					case 0x12:
						var v = pool[bi.readByte()];
						bof++;
						stack.push(toValue(v, pool));
					case 0x13:
						var v = pool[bi.readInt16()];
						bof += 2;
						stack.push(toValue(v, pool));
					case 0x14:
						var v = pool[bi.readInt16()];
						bof += 2;
						stack.push(toValue(v, pool));
					case 0x15: stack.push(lvars[bi.readByte()]); bof++;
					case 0x16: stack.push(lvars[bi.readByte()]); bof++;
					case 0x17: stack.push(lvars[bi.readByte()]); bof++;
					case 0x18: stack.push(lvars[bi.readByte()]); bof++;
					case 0x19: stack.push(lvars[bi.readByte()]); bof++; //aload
					case 0x1a: stack.push(lvars[0]);
					case 0x1b: stack.push(lvars[1]);
					case 0x1c: stack.push(lvars[2]);
					case 0x1d: stack.push(lvars[3]);
					case 0x1e: stack.push(lvars[0]);
					case 0x1f: stack.push(lvars[1]);
					case 0x20: stack.push(lvars[2]);
					case 0x21: stack.push(lvars[3]);
					case 0x22: stack.push(lvars[0]);
					case 0x23: stack.push(lvars[1]);
					case 0x24: stack.push(lvars[2]);
					case 0x25: stack.push(lvars[3]);
					case 0x26: stack.push(lvars[0]);
					case 0x27: stack.push(lvars[1]);
					case 0x28: stack.push(lvars[2]);
					case 0x29: stack.push(lvars[3]);
					case 0x2a: stack.push(lvars[0]); //aload
					case 0x2b: stack.push(lvars[1]); //aload
					case 0x2c: stack.push(lvars[2]); //aload
					case 0x2d: stack.push(lvars[3]); //aload
					case 0x2e: // load long from array
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr = switch (stack.pop()) {
							case _array(a): a.arr;
							default: throw "Not an array";
						};
						#if debug
							trace("Getting: "+ind+" from "+arr+" to get: "+arr[ind]);
						#end
						stack.push(arr[ind]);
					case 0x30: // load float from array
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr = switch (stack.pop()) {
							case _array(a): a.arr;
							default: throw "Not an array";
						};
						#if debug
							trace("Getting: "+ind+" from "+arr+" to get: "+arr[ind]);
						#end
						stack.push(arr[ind]);
					case 0x31: // load double from array
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr = switch (stack.pop()) {
							case _array(a): a.arr;
							default: throw "Not an array";
						};
						#if debug
							trace("Getting: "+ind+" from "+arr+" to get: "+arr[ind]);
						#end
						stack.push(arr[ind]);
					case 0x32: // load from array
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr = switch (stack.pop()) {
							case _array(a): a.arr;
							default: throw "Not an array";
						};
						#if debug
							trace("Getting: "+ind+" from "+arr+" to get: "+arr[ind]);
						#end
						stack.push(arr[ind]);
					case 0x33: // load bool from array
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr = switch (stack.pop()) {
							case _array(a): a.arr;
							default: throw "Not an array";
						};
						#if debug
							trace("Getting: "+ind+" from "+arr+" to get: "+arr[ind]);
						#end
						stack.push(arr[ind]);
					case 0x34: // load char from array
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr = switch (stack.pop()) {
							case _array(a): a.arr;
							default: throw "Not an array";
						};
						#if debug
							trace("Getting: "+ind+" from "+arr+" to get: "+arr[ind]);
						#end
						stack.push(arr[ind]);
					case 0x35: // load short from array
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr = switch (stack.pop()) {
							case _array(a): a.arr;
							default: throw "Not an array";
						};
						#if debug
							trace("Getting: "+ind+" from "+arr+" to get: "+arr[ind]);
						#end
						stack.push(arr[ind]);
					case 0x36: // store int
						lvars[bi.readByte()] = stack.pop(); bof++;
					case 0x37: // store long
						lvars[bi.readByte()] = stack.pop(); bof++;
					case 0x38: // store float
						lvars[bi.readByte()] = stack.pop(); bof++;
					case 0x39: // store double
						lvars[bi.readByte()] = stack.pop(); bof++;
					case 0x3a: // store reference
						lvars[bi.readByte()] = stack.pop(); bof++;
					case 0x3b: // store int at 0
						lvars[0] = stack.pop();
					case 0x3c: // store int at 1
						lvars[1] = stack.pop();
					case 0x3d: // store int at 2
						lvars[2] = stack.pop();
					case 0x3e: // store int at 3
						lvars[3] = stack.pop();
					case 0x3f: // store long at 0
						lvars[0] = stack.pop();
					case 0x40: // store long at 1
						lvars[1] = stack.pop();
					case 0x41: // store long at 2
						lvars[2] = stack.pop();
					case 0x42: // store long at 3
						lvars[3] = stack.pop();
					case 0x43: // store float at 0
						lvars[0] = stack.pop();
					case 0x44: // store float at 1
						lvars[1] = stack.pop();
					case 0x45: // store float at 2
						lvars[2] = stack.pop();
					case 0x46: // store float at 3
						lvars[3] = stack.pop();
					case 0x47: // store double at 0
						lvars[0] = stack.pop();
					case 0x48: // store double at 1
						lvars[1] = stack.pop();
					case 0x49: // store double at 2
						lvars[2] = stack.pop();
					case 0x4a: // store double at 3
						lvars[3] = stack.pop();
					case 0x4b: lvars[0] = stack.pop();
					case 0x4c: lvars[1] = stack.pop();
					case 0x4d: lvars[2] = stack.pop();
					case 0x4e: lvars[3] = stack.pop();
					case 0x4f: // store int into array
						var val:JValue = stack.pop();
						var ind:Int = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr:JValue = stack.pop();
						#if debug
							trace("Value: "+val);
						#end
						switch(arr) {
							case _array(a): a.arr[ind] = val;
							default: throw "Not an array";
						}
					case 0x50: // store long into array
						var val:JValue = stack.pop();
						var ind:Int = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr:JValue = stack.pop();
						#if debug
							trace("Value: "+val);
						#end
						switch(arr) {
							case _array(a): a.arr[ind] = val;
							default: throw "Not an array";
						}
					case 0x51: // store float into array
						var val:JValue = stack.pop();
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr:JValue = stack.pop();
						#if debug
							trace("Value: "+val);
							trace("Index: "+ind);
						#end
						switch(arr) {
							case _array(a): a.arr[ind] = val;
							default: throw "Not an array";
						}
					case 0x52: // store double into array
						var val:JValue = stack.pop();
						var ind:Int = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr:JValue = stack.pop();
						#if debug
							trace("Value: "+val);
						#end
						switch(arr) {
							case _array(a): a.arr[ind] = val;
							default: throw "Not an array";
						}
					case 0x53: // store reference into array
						var val:JValue = stack.pop();
						var ind:Int = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						var arr:JValue = stack.pop();
						#if debug
							trace("Value: "+val);
						#end
						switch(arr) {
							case _array(a): a.arr[ind] = val;
							default: throw "Not an array";
						}
					case 0x54: // store byte/boolean into array
						var val = stack.pop();
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						toNativeValue(stack.pop())[ind] = val;
					case 0x55: // store char into array
						var val = stack.pop();
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						toNativeValue(stack.pop())[ind] = val;
					case 0x56: // store short into array
						var val = stack.pop();
						var ind = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						toNativeValue(stack.pop())[ind] = val;
					case 0x57: stack.pop();
					case 0x58:
						var v = stack.pop();
						stack.push(copy(v));
						stack.push(v);
					case 0x59:
						var v = stack.pop();
						stack.push(v);
						stack.push(copy(v));
					case 0x5a:
						var top = stack.pop();
						var middle = stack.pop();
						stack.push(copy(top));
						stack.push(middle);
						stack.push(top);
					case 0x5b:
						var top = stack.pop();
						var middle = stack.pop();
						var btm = null;
						if(!dor_long(top))
							btm = stack.pop();
						stack.push(copy(top));
						if(btm != null)
							stack.push(btm);
						stack.push(middle);
						stack.push(top);
					case 0x5c:
						var top = stack.pop();
						if(dor_long(top)) {
							stack.push(copy(top));
							stack.push(top);
						} else {
							var mid = stack.pop();
							stack.push(copy(top));
							stack.push(copy(mid));
							stack.push(copy(top));
						}
					case 0x5d:
						var top = stack.pop();
						var mid = dor_long(top) ? null : stack.pop();
						var btm = stack.pop();
						var btm2 = dor_long(btm) ? null : stack.pop();
						if(mid == null) {
							stack.push(copy(top));
							stack.push(btm);
							stack.push(top);
						} else {
							stack.push(copy(mid));
							stack.push(copy(top));
							stack.push(btm);
							stack.push(mid);
							stack.push(top);
						}
					case 0x5e:
						var top = stack.pop();
						var mid = dor_long(top) ? null : stack.pop();
						var mid2 = stack.pop();
						var mid3 = dor_long(mid2) ? null : stack.pop();
						var btm = stack.pop();
						var btm2 = dor_long(btm) ? null : stack.pop();
						if(mid == null) {
							stack.push(copy(top));
							if(mid3 != null)
								stack.push(mid3);
							stack.push(mid2);
							stack.push(top);
						} else {
							stack.push(copy(mid));
							stack.push(copy(top));
							stack.push(btm);
							if(mid3 != null)
								stack.push(mid3);
							stack.push(mid2);
							stack.push(mid);
							stack.push(top);
						}
					case 0x5f:
						var top = stack.pop();
						var btm = stack.pop();
						stack.push(top);
						stack.push(btm);
					case 0x60: // int add
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._int(Int32.add(a, b)));
					case 0x61: // long add
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.add(a, b)));
					case 0x62: // float add
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._float(a + b));
					case 0x63: // double add
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._double(a + b));
					case 0x64: // int subtract
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._int(Int32.sub(a, b)));
					case 0x65: // long subtract
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.sub(a, b)));
					case 0x66: // float subtract
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._float(a - b));
					case 0x67: // double subtract
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._double(a - b));
					case 0x68: // int multiply
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._int(Int32.mul(a, b)));
					case 0x69: // long multiply
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.mul(a, b)));
					case 0x6a: // float multiply
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._float(a * b));
					case 0x6b: // double multiply
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._double(a * b));
					case 0x6c: // int divide
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._int(Int32.div(a, b)));
					case 0x6d: // long divide
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.div(a, b)));
					case 0x6e: // float divide
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._float(a / b));
					case 0x6f: // double divide
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._double(a / b));
					case 0x70: // int remainder
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._int(Int32.mod(a, b)));
					case 0x71: // long remainder
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.mod(a, b)));
					case 0x72: // float remainder
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._float(a % b));
					case 0x73: // double remainder
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Float) || !Std.is(b, Float))
							throw "Type Error";
						stack.push(JValue._double(a % b));
					case 0x74: // negate int
						stack.push(JValue._int(Int32.neg(toNativeValue(stack.pop()))));
					case 0x75: // negate long
						stack.push(JValue._long(Int64.neg(toNativeValue(stack.pop()))));
					case 0x76: // negate float
						stack.push(JValue._float(-toNativeValue(stack.pop())));
					case 0x77: // negate double
						stack.push(JValue._double(-toNativeValue(stack.pop())));
					case 0x78: // int shift left
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						
						stack.push(JValue._int(Int32.shl(a, Int32.toInt(b))));
					case 0x79: // long shift left
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.shl(a, Int64.toInt(b))));
					case 0x7a: // int arithmetic shift right
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						
						stack.push(JValue._int(Int32.ushr(a, Int32.toInt(b))));
					case 0x7b: // long arithmetic shift right
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.ushr(a, Int64.toInt(b))));
					case 0x7c: // int logical shift right
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						
						stack.push(JValue._int(Int32.shr(a, Int32.toInt(b))));
					case 0x7d: // long logical shift right
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.shr(a, Int64.toInt(b))));
					case 0x7e: // int bitwise and
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						
						stack.push(JValue._int(Int32.and(a, b)));
					case 0x7f: // long bitwise and
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.and(a, b)));
					case 0x80: // int bitwise or
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						
						stack.push(JValue._int(Int32.or(a, b)));
					case 0x81: // long bitwise or
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.or(a, b)));
					case 0x82: // int bitwise xor
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						
						stack.push(JValue._int(Int32.xor(a, b)));
					case 0x83: // long bitwise xor
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						if(!Std.is(a, Int64) || !Std.is(b, Int64))
							throw "Type Error";
						stack.push(JValue._long(Int64.xor(a, b)));
					case 0x84: // lvar increment
						var ind = bi.readByte();
						var inc = bi.readByte();
						#if debug
							trace("Incrementing "+lvars[ind]+" by "+inc);
						#end
						var old:JValue = lvars[ind];
						lvars[ind] = switch(old) {
							case _int(v): JValue._int(Int32.add(v, Int32.ofInt(inc)));
							case _long(v): JValue._long(Int64.add(v, Int64.ofInt(inc)));
							case _float(v): JValue._float(v+inc);
							case _double(v): JValue._double(v+inc);
							case _short(v): JValue._short(v+inc);
							case _char(v): JValue._char(v+inc);
							case _byte(v): JValue._byte(v+inc);
							default: throw "Unable to increment this value";
						};
						#if debug
							trace("\t\tto get "+lvars[ind]);
						#end
					case 0x85: // int to long
						stack.push(JValue._long(Int64.ofInt32(toNativeValue(stack.pop()))));
					case 0x86: // int to float
						stack.push(JValue._float(cast Int32.toInt(toNativeValue(stack.pop()))));
					case 0x87: // int to double
						stack.push(JValue._double(cast Int32.toInt(toNativeValue(stack.pop()))));
					case 0x88: // long to int
						stack.push(JValue._int(Int32.ofInt(Int64.toInt(toNativeValue(stack.pop())))));
					case 0x89: // long to float
						stack.push(JValue._float(cast Int64.toInt(toNativeValue(stack.pop()))));
					case 0x8a: // long to double
						stack.push(JValue._double(cast Int64.toInt(toNativeValue(stack.pop()))));
					case 0x8b: // float to int
						stack.push(JValue._int(Int32.ofInt(Std.int(toNativeValue(stack.pop())))));
					case 0x8c: // float to long
						stack.push(JValue._long(Int64.ofInt(Std.int(toNativeValue(stack.pop())))));
					case 0x8d: // float to double
						stack.push(JValue._double(toNativeValue(stack.pop())));
					case 0x8e: // double to int
						stack.push(JValue._int(Int32.ofInt(Std.int(JVM.toNativeValue(stack.pop())))));
					case 0x8f: // double to long
						stack.push(JValue._long(Int64.ofInt(Std.int(JVM.toNativeValue(stack.pop())))));
					case 0x90: // double to float
						stack.push(JValue._float(toNativeValue(stack.pop())));
					case 0x91: // int to byte
						stack.push(JValue._byte(Int32.toInt(toNativeValue(stack.pop()))));
					case 0x92: // int to char
						stack.push(JValue._char(Int32.toInt(toNativeValue(stack.pop()))));
					case 0x93: // int to short
						stack.push(JValue._short(Int32.toInt(toNativeValue(stack.pop()))));
					case 0x94: // compare longs
						stack.push(JValue._long(Int64.ofInt(Int64.compare(toNativeValue(stack.pop()), toNativeValue(stack.pop())))));
					case 0x95: // compare floats
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._float(Math.abs(a - b)));
					case 0x96: // compare floats
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._float(Math.abs(a - b)));
					case 0x97: // compare doubles
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._double(Math.abs(a - b)));
					case 0x98: // compare doubles
						var a = toNativeValue(stack.pop()), b = toNativeValue(stack.pop());
						stack.push(JValue._double(Math.abs(a - b)));
					case 0x99: // if 0
						var off = bof + bi.readInt16();
						bof += 2;
						if(switch(stack.pop()) {
							case _float(v): v == 0;
							case _double(v): v == 0;
							case _int(v): Int32.isZero(v);
							case _long(v): Int64.isZero(v);
							case _short(v): v == 0;
							case _byte(v): v == 0;
							case _char(v): v == 0;
							default: throw "Not a number!";
						}) {
							bi.close();
							bof=off; 
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0x9a: // if not 0
						var off = bof + bi.readInt16();
						bof += 2;
						if(switch(stack.pop()) {
							case _float(v): v != 0;
							case _double(v): v != 0;
							case _int(v): !Int32.isZero(v);
							case _long(v): !Int64.isZero(v);
							case _short(v): v != 0;
							case _byte(v): v != 0;
							case _char(v): v != 0;
							default: throw "Not a number!";
						}) {
							bi.close();
							bof=off; 
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0x9b: // if less than 0
						var off = bof + bi.readInt16();
						bof += 2;
						if(switch(stack.pop()) {
							case _float(v): v < 0;
							case _double(v): v < 0;
							case _int(v): Int32.isNeg(v);
							case _long(v): Int64.isNeg(v);
							case _short(v): v < 0;
							case _byte(v): v < 0;
							case _char(v): v < 0;
							default: throw "Not a number!";
						}) {
							bi.close();
							bof=off; 
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0x9c: // if greater than or equal to than 0
						var off = bof + bi.readInt16();
						bof += 2;
						if(switch(stack.pop()) {
							case _float(v): v >= 0;
							case _double(v): v >= 0;
							case _int(v): !Int32.isNeg(v) || Int32.isZero(v);
							case _long(v): !Int64.isNeg(v) || Int64.isZero(v);
							case _short(v): v >= 0;
							case _byte(v): v >= 0;
							case _char(v): v >= 0;
							default: throw "Not a number!";
						}) {
							bi.close();
							bof=off; 
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0x9d: // if greater than 0
						var off = bof + bi.readInt16();
						bof += 2;
						if(switch(stack.pop()) {
							case _float(v): v > 0;
							case _double(v): v > 0;
							case _int(v): !Int32.isNeg(v);
							case _long(v): !Int64.isNeg(v);
							case _short(v): v > 0;
							case _byte(v): v > 0;
							case _char(v): v > 0;
							default: throw "Not a number!";
						}) {
							bi.close();
							bof=off;
							 trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0x9e: // if less than or equal to 0
						var off = bof + bi.readInt16();
						bof += 2;
						if(switch(stack.pop()) {
							case _float(v): v <= 0;
							case _double(v): v <= 0;
							case _int(v): Int32.isNeg(v) || Int32.isZero(v);
							case _long(v): Int64.isNeg(v) || Int64.isZero(v);
							case _short(v): v <= 0;
							case _byte(v): v <= 0;
							case _char(v): v <= 0;
							default: throw "Not a number!";
						}) {
							bi.close();
							bof=off; 
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0x9f: // if ints are equal
						var off = bof + bi.readInt16();
						bof += 2;
						if(Int32.isZero(Int32.sub(toNativeValue(stack.pop()), toNativeValue(stack.pop())))) {
							bi.close();
							bof=off; 
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0xa0: // if ints are not equal
						var off = bof + bi.readInt16();
						bof += 2;
						if(!Int32.isZero(Int32.sub(toNativeValue(stack.pop()), toNativeValue(stack.pop())))) {
							bi.close();
							bof=off; 
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0xa1: // if bottom less than top
						var top = switch(stack.pop()) {
							case _int(v): v;
							default: throw "Can only compare int values";
						};
						var btm = switch(stack.pop()) {
							case _int(v): v;
							default: throw "Can only compare int values";
						};
						var off = bof + bi.readInt16();
						bof += 2;
						if(Int32.isNeg(Int32.sub(btm, top))) {
							bi.close();
							bof=off;
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0xa2: // if bottom greater than or equal to top
						var top:Int32 = switch(stack.pop()) {
							case _int(v): v;
							default: throw "Can only compare int values";
						};
						var btm:Int32 = switch(stack.pop()) {
							case _int(v): v;
							default: throw "Can only compare int values";
						};
						var off = bof + bi.readInt16();
						if(!Int32.isNeg(Int32.sub(btm, top)) || Int32.isZero(Int32.sub(top, btm))) {
							bi.close();
							bof=off;
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						} else {
							#if debug
								trace(btm+" is not greater than or equal to "+top+" so not jumping to "+off);
							#end
						}
						bof += 2;
					case 0xa3: // if bottom greater than top
						var top = switch(stack.pop()) {
							case _int(v): v;
							default: throw "Can only compare int values";
						};
						var btm = switch(stack.pop()) {
							case _int(v): v;
							default: throw "Can only compare int values";
						};
						var off = bof + bi.readInt16();
						bof += 2;
						if(!Int32.isNeg(Int32.sub(btm, top))) {
							bi.close();
							bof=off; 
							trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0xa4: // if bottom less than or equal to top
						var top = switch(stack.pop()) {
							case _int(v): v;
							default: throw "Can only compare int values";
						};
						var btm = switch(stack.pop()) {
							case _int(v): v;
							default: throw "Can only compare int values";
						};
						var off = bof + bi.readInt16();
						bof += 2;
						if(Int32.isNeg(Int32.sub(btm, top)) || Int32.isZero(Int32.sub(top, btm))) {
							bi.close();bof=off; trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0xa5: // if bottom equal to top
						var off = bof + bi.readInt16();
						bof += 2;
						if(toNativeValue(stack.pop()) == toNativeValue(stack.pop())) {
							bi.close();bof=off; trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0xa6: // if bottom not equal to top
						var off = bof + bi.readInt16();
						bof += 2;
						if(toNativeValue(stack.pop()) != toNativeValue(stack.pop())) {
							bi.close();bof=off; trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						}
					case 0xa7: // goto
						var off = bof + bi.readInt16();
						bof += 2;
						#if debug
							trace("Attempting to jump from "+bof+" to "+off);
						#end
						bi.close();
						bof = off;
						trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
						bi.bigEndian = true;
						continue;
					case 0xa8: // goto and put address on stack
						stack.push(JValue._int(Int32.ofInt(bof)));
						var off = bof + bi.readInt16();
						bof += 2;
						bi.close();
						trace("Jumping to: "+bof); bi = new haxe.io.BytesInput(b, off); 
					case 0xa9:
						var id = lvars[bi.readByte()];
						bof++;
						bi.close();
						bi = new haxe.io.BytesInput(b, id);
					case 0xaa: throw "Unimplemented"; // table switch
					case 0xab: throw "Unimplemented"; // lookup switch
					case 0xac:
						bi.close();
						return stack.pop();
					case 0xad:
						bi.close();
						return stack.pop();
					case 0xae:
						bi.close();
						return stack.pop();
					case 0xaf:
						bi.close();
						return stack.pop();
					case 0xb0:
						bi.close();
						return stack.pop();
					case 0xb1:
						bi.close();
						#if debug
							trace("Returning void");
						#end
						return JValue._void; //_void;
					case 0xb2: // get static field
						switch(pool[bi.readInt16()]) {
							case fieldref(c, nt):
								var n = stringConst(pool[resolveRef(pool[c])]);
								if(externs.exists(n)) {
									var c = externs.get(n);
									var mn = stringConst(pool[gname(pool[nt])]);
									var gn = "get" + mn.charAt(0).toUpperCase() + mn.substr(1);
									trace(mn);
									stack.push(fromNativeValue(
										if(Reflect.field(c, mn) != null)
											Reflect.field(c, mn);
										else if(Reflect.field(c, gn) != null)
											Reflect.callMethod(c, Reflect.field(c, gn), []);
										else
											throw "Attempt to get unexistant static field "+n+" in "+c
									));
								} else if(classes.exists(n))
									stack.push(classes.get(n).getField(pool[nt]).value);
								#if debug
									trace("Field found in "+n+": "+stack[stack.length-1]);
								#end
							default: throw "Not a field reference!";
						}
						bof += 2;
					case 0xb3: // put static
						var field = pool[bi.readInt16()];
						bof += 2;
						var value = stack.pop();
					case 0xb4: // get instance field
						var obj = switch(stack.pop()) {
							case _object(o): o;
							default: throw "Invalid Object";
						};
						if(obj == null)
							throw new jax.lang.JNullPointerException("Cannot get instance field of null");
						var field = pool[bi.readInt16()]; // field reference
						var f  =obj.getField(field);
						stack.push(f == null ? JValue._object(null) : f.value);
					case 0xb5: // put field
						var fieldref = pool[bi.readInt16()];
						bof += 2;
						var val = stack.pop();
						var obj = stack.pop();
						#if debug
							trace("Putting "+val+" into "+obj);
						#end
						switch(obj) {
							case _object(o): if(o == null)
								throw new jax.lang.JNullPointerException("Cannot put field into null");
							else
								o.getField(fieldref).value = val;
							case _exter(o): Reflect.setField(o, toNativeValue(toValue(pool[gname(fieldref)], pool)), toNativeValue(val));
							default:
						}
					case 0xb6: // invoke virtual/instance method
						var obj = stack[0];
						
						stack = stack.slice(1);
						switch(pool[bi.readInt16()]) {
							case methodref(n, f):
								var c = stringConst(pool[resolveRef(pool[n])]);
								#if debug
									trace("Class: "+c);
									trace("Instance: "+obj);
									trace("Method: "+toNativeValue(toValue(pool[gname(pool[f])])));
									trace("Args: "+Std.string(stack));
								#end
								switch(obj) {
									case _object(o):
										#if debug
											trace("Java Object");
										#end
										var cl = classes.get(c);
										var method = cl.getMethod(pool[f], pool);
										#if debug
										#end
									case _exter(o): 
										#if debug
											trace("External Object");
										#end
										var args = Lambda.array(Lambda.map(stack, toNativeValue));
										#if debug
											trace("Calling External Object with args: "+Std.string(args));
										#end
										stack = [];
										var ntv:Dynamic =Reflect.callMethod(o, Reflect.field(o, toNativeValue(toValue(pool[gname(pool[f])]))), args);
										if(ntv != null)
											stack.push(fromNativeValue(ntv));
									default: throw "Invalid Type";
								}
							default:
						}
						bof += 2;
					case 0xb7: // call method
						var method = pool[bi.readInt16()];
						bof += 2;
						var obj = stack.pop();
						var res = solveMethodRef(obj, pool, method, stack);
						if(res != null && res != JValue._void)
							stack.push(res);
					case 0xb8: // invoke static
						var val = pool[bi.readInt16()]; // method ref
						bof += 2;
						var args = [];
						while(stack.length > 0)
							args.push(stack.pop());
						stack.push(switch(val) {
							case methodref(c, nt):
								var cl = stringConst(pool[resolveRef(pool[c])]);
								var n = stringConst(pool[gname(pool[nt])]);
								#if debug
									trace("Calling "+n+" with args: "+stack);
								#end
								if(externs.exists(cl)) {
									var field = Reflect.field(externs.get(cl), n);
									if(field == null)
										throw "Unexistant field: "+n+" in "+cl;
									var res = Reflect.callMethod(externs.get(cl), field, args);
									
									JVM.fromNativeValue(res);
								} else if(classes.exists(cl)) {
									var r = null;
									for(n in classes.get(cl).methods) {
										if(toNativeValue(toValue(pool[n.name_index])) == n)
											for(a in n.attributes)
												switch(a) {
													case Code(c): r = JVM.exec(classes.get(cl), c.code, args);
													default:
												}
									}
									r;
								}
							default:
								throw "Expected field reference.";
						});
						
					case 0xba: // invoke dynamic
						var index = bi.readInt16();
						if(bi.readInt16() != 0)
							throw "Invalid dynamic invocation";
						bof += 4;
						var method = pool[index];
					case 0xbb: //new object
						var type = switch(pool[solveClassRef(pool[bi.readInt16()])]) {
							case str(s): s;
							default: throw "Not a string";
						};
						#if debug
						trace("Constructing object of type: "+type);
						#end
						if(classes.exists(type)) {
							var obj = new jax.lang.JObject();
							obj._type = type;
							jax.lang.JObject.init(obj, classes.get(obj._type));
							stack.push(JValue._object(obj));
						} else {
							stack.push(JValue._object(null));
						}
					case 0xbc: // new primitive array
						var length = haxe.Int32.toInt(switch(stack.pop()) {
							case _int(v): v;
							default: throw "Index must be int";
						});
						#if debug
							trace("Length: "+length);
						#end
						var type = bi.readByte();
						bof += 2;
						#if debug
							trace("New array of type "+type);
						#end
						stack.push(JValue._array(new JArray(length)));
					case 0xbd: // new array
						var lenval = stack.pop();
						var length:Int = Int32.toInt(switch(lenval) {
							case _int(v): v;
							default: throw "Length must be int";
						});
						#if debug
							trace("Constructing array of length: "+length);
						#end
						var type = bi.readInt16();
						bof += 2;
						stack.push(JValue._array(new JArray(length)));
					case 0xbe: // get array length
						stack.push(JValue._int(Int32.ofInt(switch(stack.pop()) {
							case _array(a): a.length;
							default: throw "This is not an array!";
						})));
					case 0xbf: // throw exception
						jax.lang.JSystem.err.println("Exception: "+stack.pop());
						return JValue._void;
					case 0xc0: // check cast
						var obj = stack.pop();
						var cl = pool[resolveRef(pool[bi.readInt16()])];
						#if debug
							trace("Attempt to check cast");
							trace("on "+obj);
							trace("if "+cl);
							trace("CAST UNIMPLEMENTED");
						#end
						bof += 2;
					case 0xc1: // instance of
						var obj = stack.pop();
						var cl = pool[resolveRef(pool[bi.readInt16()])];
						#if debug
							trace("Attempt to check instance");
							trace("on "+obj);
							trace("if "+cl);
							trace("INSTANCE OF UNIMPLEMENTED");
						#end
						bof += 2;
					case 0xc5: // new multidimensional array
						var cl = bi.readInt16();
						var dims = bi.readByte();
						#if debug
							trace("Constructing multi-dimension array : "+stack);
						#end
						var arr = initArray(dims, stack);
						while(stack.length > 0)
							stack.pop();
						stack.push(arr);
					case 0xca:
					default: throw ("Unknown instruction: "+StringTools.hex(byte));
				}
				#if debug
					trace(bof + ": "+StringTools.hex(byte)+":: "+stack+" :: "+lvars);
				#end
			}
		} catch(d:Dynamic){}
		return JValue._void;
	}
	static inline function dor_long(v:JValue) {
		return switch(v) {
			case _long(v): true;
			case _double(v): true;
			default: false;
		}
	}
	static function initArray(dims:Int, stack:Array<JValue>):JValue {
		dims--;
		var len = switch(stack[0]) {
			case _int(v): Int32.toInt(v);
			default: 0;
		};
		stack = stack.slice(1);
		#if debug
			trace("Constructing array of length: "+len+" and dimensions: "+dims);
		#end
		var arr = new Array<JValue>();
		for(i in 0...len) {
			arr.push(
				dims <= 0 ? JValue._object(null) : initArray(dims, stack));
		}
		return JValue._array(JArray.ofArray(arr));
	}
	public static function fromNativeValue(d:Dynamic):JValue {
		#if debug
			trace("Converting "+d+" of type "+Type.typeof(d));
		#end
		switch(Type.typeof(d)) {
			case TUnknown: return JValue._void;
			case TObject: return JValue._exter(d);
			case TNull: return JValue._object(null);
			case TInt: return JValue._int(Int32.ofInt(d));
			case TFunction: return JValue._exter(d);
			case TFloat: return JValue._double(d);
			case TBool: return JValue._boolean(d);
			case TClass(c): return JValue._exter(d);
			case TEnum(e): return JValue._exter(d);
		};
	}
	inline static function copy(v:JValue) {
		switch(v) {
			case _object(o): return JValue._object(o);
			case _array(c): return JValue._array(c);
			case _exter(o): return JValue._exter(o);
			case _void: return JValue._void;
			default: return fromNativeValue(Type.enumParameters(v)[0]);
		}
	}
	public inline static function stringConst(s:JConstant) {
		return switch(s) {
			case str(s): s;
			default: throw "Not a string";
		}
	}
	static function solveMethodRef(obj:JValue, pool:Array<JConstant>, method:JConstant, args:Array<JValue>) {
		JVM.gc();
		switch(method) {
			case methodref(c, nt):
				var c = toNativeValue(toValue(pool[JVM.resolveRef(pool[c])]));
				var n = toNativeValue(toValue(pool[gname(pool[nt])]));
				#if debug
					trace("Attempting to call method "+n+" in object "+obj+" of type "+c);
				#end
				if(externs.exists(c)) {
					var nargs = Lambda.array(Lambda.map(args, toNativeValue));
					#if debug
						trace("Calling "+c+"."+n+" with "+nargs);
					#end
					if(n == "<init>")
						return JValue._exter(Type.createInstance(externs.get(c), args));
					else
						return fromNativeValue(Reflect.callMethod(externs.get(c), Reflect.field(obj, n), nargs));
				} else if(classes.exists(c)) {
					var m = classes.get(c).getMethod(pool[nt], pool);
					var n = toNativeValue(toValue(pool[gname(pool[nt])]));
					var pool = classes.get(c).const_pool;
					for(att in m.attributes) {
						var code:JCode = null;
						switch(att) {
							case Code(c): code = c;
							default:
						}
						if(code == null)
							throw "Code not found";
						else {
							var res = JVM.exec(classes.get(c), code.code, args);
							#if debug
								trace("Result of running "+n+" with args "+args+": "+res);
							#end
							return res;
						}
					}
				}
			default:
				throw "Invalid method reference";
		}
		return JValue._void;
	}
	inline static function gname(c:JConstant) {
		return switch(c) {
			case nameandtype(n, t): n;
			default: throw "Invalid name & type";
		}
	}
	inline static function solveClassRef(ref:JConstant) {
		return switch(ref) {
			case classref(i): i;
			default: throw "Invalid class reference";
		}
	}
}